package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author atguigu-mqx
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {
    //  需要mapper 么? 不需要！ 凌晨已经将数据放入缓存中了。只需要查询缓存数据即可！
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<SeckillGoods> findAll() {
        //  hvals key
        List<SeckillGoods> seckillGoodsList = this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
        return seckillGoodsList;
    }

    @Override
    public SeckillGoods findSeckillGoodsBySkuId(Long skuId) {
        //  hget key field  注意点：field 数据类型要一致！
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
        return seckillGoods;
    }

    @Override
    public void seckillOrder(Long skuId, String userId) {
        //  将这个用户购买的哪个商品存储到缓存中！
        /*
        1.	验证状态位！
        2.	判断用户是否下过订单！
        3.	能够从商品的剩余库存中获取到数据！
        4.	才能将数据保存到缓存中！
        5.  减库存操作！
         */
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return;
        }
        //  判断用户是否下过订单！ 一个用户只能购买一次！ setnx key value
        //  见名之意： key = seckill:user:userId value = skuId
        String seckillUserKey = RedisConst.SECKILL_USER+userId;
        Boolean result = this.redisTemplate.opsForValue().setIfAbsent(seckillUserKey, skuId, RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  判断  result = true 表示这个命令生效了！ 意味着这个用户是头一次秒杀！
        //  result = false ;表示缓存中已经有这个key了，说明用户已经秒杀过！
        if(!result){
            return;
        }
        //  从缓存中存储商品的队列中获取一个数据！
        //  key = seckill:stock:
        String skuIdExist = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + skuId);

        // String skuIdExist = (String) this.redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();
        if (StringUtils.isEmpty(skuIdExist)){
            //  说明没有剩余库存了！
            this.redisTemplate.convertAndSend("seckillpush",skuId+":0");
            return;
        }
        //  向缓存中保存数据！
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userId);
        orderRecode.setSeckillGoods(this.findSeckillGoodsBySkuId(skuId));
        orderRecode.setNum(1);
        orderRecode.setOrderStr(MD5.encrypt(userId+skuId));
        //  key =  seckill:orders   field = userId value = orderRecode
        String seckillOrderKey = RedisConst.SECKILL_ORDERS;
        this.redisTemplate.opsForHash().put(seckillOrderKey,userId,orderRecode);

        //  如果上述都成功！还需要做减库存操作！  涉及到两点 ， redis | mysql
        this.updateStockCount(skuId);
    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        //  判断用户在缓存中是否存在！
        Boolean flag = this.redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);
        //  flag = true; 则表示用户在缓存中 存在！
        if (flag){
            //  判断用户是否抢单成功！
            Boolean result = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);
            //  hget key field
            //  OrderRecode orderRecode = (OrderRecode) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
            //  表示当前用户抢单成功！有预下单记录，有资格了！
            if (result){
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
                //  抢购成功，去下单！
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }
        //  判断用户是否下过订单！
        //  hash key = "seckill:orders:users" field = userId value = orderId
        Boolean isExistOrderId = this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);
        //  isExistOrderId = true
        if (isExistOrderId){
            //  说明这个用户已经下过订单了！
            String orderId = (String) this.redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);
            //  抢购成功，我的订单！
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }
        //  判断状态位！
        String status = (String) CacheHelper.get(skuId.toString());
        if ("0".equals(status)){
            //  已售罄！
            return Result.build(null,ResultCodeEnum.SECKILL_FINISH);
        }
        return  Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 更新库存数据！
     * @param skuId
     */
    @Transactional(rollbackFor = Exception.class) // 如果有异常则回滚！
    public void updateStockCount(Long skuId) {
        //        如果单节点： 8080
        //        Lock lock = new ReentrantLock();
        //        lock.lock();
        //        lock.unlock();
        //  如果集群部署： 8081 8082 8083
        RLock lock = this.redissonClient.getLock(skuId + ":lock");
        //  上锁
        lock.lock();
        try {
            //  获取剩余库存！
            Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);

            //  秒杀的时候会有并发！频繁更新数据库 缓存！
            if (count%2==0){
                //  mysql
                SeckillGoods seckillGoods = this.findSeckillGoodsBySkuId(skuId);
                seckillGoods.setStockCount(count.intValue());
                this.seckillGoodsMapper.updateById(seckillGoods);
                //  redis!  hset key field seckillGoods
                this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS,skuId.toString(),seckillGoods);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //  解锁：
            lock.unlock();
        }
    }
}
